﻿using DataService.Core.Json;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;

namespace App.API.Security
{
    /// <summary>
    /// Token上下文，负责token的创建和验证
    /// </summary>
    public class JWTTokenContext
    {

        /// <summary>
        /// （issuer，颁发者,发送数据人，标识发送 JWT 主体）
        /// </summary>
        public const string issuer = "DataServiceAppAPI";
        /// <summary>
        /// （audience，数据消息的接收者）
        /// </summary>
        public const string audience = "http://localhost:12346/Services";
        /// <summary>
        /// （subject，数据消息主题）
        /// </summary>
        public const string sub = "SubAppAPI";
        /// <summary>
        /// 秘钥，可以从配置文件中获取
        /// </summary>
        public static string securityKey = "6!Kr_*&^ljhKIHF845602hcSA@qGKJ*IU%JYIULG:LIFKIGFGOkgol85*&$GMZ3siu@R4j2Bt!Q$O7fSWtcsQS7DzNlA7LGg@89PcvHocmsLJFdjhhjm4585lGpcq@NlOEAlge";


        public static string GetToken(string loginId)
        {
            Dictionary<string, object> payLoad = new Dictionary<string, object>();
            payLoad.Add("sub", sub);
            payLoad.Add("jti", Guid.NewGuid());
            payLoad.Add("audlogin", loginId);
            payLoad.Add("issuer", issuer);
            payLoad.Add("audience", audience);
            string loginToken = CreateToken(payLoad, 60 *3);
            return loginToken;
        }

        /// <summary>
        /// 创建jwttoken,源码自定义
        /// </summary>
        /// <param name="payLoad"></param>
        /// <param name="header"></param>
        /// <returns></returns>
        public static string CreateToken(Dictionary<string, object> payLoad, int expiresMinute, Dictionary<string, object> header = null)
        {
            if (header == null)
            {
                header = new Dictionary<string, object>(new List<KeyValuePair<string, object>>() {
                    new KeyValuePair<string, object>("alg", "HS256"),
                    new KeyValuePair<string, object>("typ", "JWT"),
                    new KeyValuePair<string, object>("jti", Guid.NewGuid())
                });
            }
            //添加jwt可用时间（应该必须要的）
            var now = DateTime.UtcNow;
            payLoad["nbf"] = ToUnixEpochDate(now);//可用时间起始
            //数据消息的过期时间，一般采用时间戳格式
            payLoad["exp"] = ToUnixEpochDate(now.Add(TimeSpan.FromMinutes(expiresMinute)));//可用时间结束


            var encodedHeader = Base64UrlEncoder.Encode(JsonHelper.ToJson(header));
            var encodedPayload = Base64UrlEncoder.Encode(JsonHelper.ToJson(payLoad));

            var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(securityKey));
            var encodedSignature = Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(encodedHeader, ".", encodedPayload))));

            var encodedJwt = string.Concat(encodedHeader, ".", encodedPayload, ".", encodedSignature);
            return encodedJwt;
        }

        /// <summary>
        /// 创建jwtToken,采用微软内部方法，默认使用HS256加密，如果需要其他加密方式，请更改源码
        /// 返回的结果和CreateToken一样
        /// </summary>
        /// <param name="payLoad"></param>
        /// <param name="expiresMinute">有效分钟</param>
        /// <returns></returns>
        private static string CreateTokenByHandler(Dictionary<string, object> payLoad, int expiresMinute)
        {

            var now = DateTime.UtcNow;

            // Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims.
            // You can add other claims here, if you want:
            var claims = new List<Claim>();
            foreach (var key in payLoad.Keys)
            {
                var tempClaim = new Claim(key, payLoad[key]?.ToString());
                claims.Add(tempClaim);
            }
            var jwt = new JwtSecurityToken(
                 issuer: null,
                 audience: null,
                 claims: claims,
                 notBefore: now,
                 expires: now.Add(TimeSpan.FromMinutes(expiresMinute)),
                 signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(securityKey)), SecurityAlgorithms.HmacSha256));
            var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
            return encodedJwt;
        }

        /// <summary>
        /// 验证身份 验证签名的有效性,
        /// </summary>
        /// <param name="encodeJwt"></param>
        /// <param name="validatePayLoad">自定义各类验证； 是否包含那种申明，或者申明的值， </param>
        /// 例如：payLoad["aud"]?.ToString() == "roberAuddience";
        /// 例如：验证是否过期 等
        /// <returns></returns>
        public static bool Validate(string encodeJwt, Func<Dictionary<string, object>, bool> validatePayLoad)
        {
            var success = true;
            var jwtArr = encodeJwt.Split('.');
            if (jwtArr.Length == 0) return false;
            try
            {
                var header = JsonHelper.ToJsonEntity<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[0]));
                var payLoad = JsonHelper.ToJsonEntity<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[1]));
                var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(securityKey));
                //首先验证签名是否正确（必须的）
                success = success && string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1])))));
                if (!success)
                {
                    return success;//签名不正确直接返回
                }
                //其次验证是否在有效期内（也应该必须）
                var now = ToUnixEpochDate(DateTime.UtcNow);
                success = success && (now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString()));

                //再其次 进行自定义的验证
                success = success && validatePayLoad(payLoad);

                return success;
            }
            catch (Exception ex)
            {
                return false;
                throw ex;
            }

        }
        /// <summary>
        /// 获取jwt中的payLoad
        /// </summary>
        /// <param name="encodeJwt"></param>
        /// <returns></returns>
        public static Dictionary<string, object> GetPayLoad(string encodeJwt)
        {
            var jwtArr = encodeJwt.Split('.');
            var payLoad = JsonHelper.ToJsonEntity<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[1]));
            return payLoad;
        }
        public static long ToUnixEpochDate(DateTime date) =>
            (long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds);
    }
}
